#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <math.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <getopt.h>
#include <signal.h>
#include <sys/mman.h>


#include "ot_adc.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* __cplusplus */

#define DEV_FILE "/dev/ot_lsadc"

/* 温度系数AD (temperature coefficient) */
#define TEMPERATURE_AD  "/dev/ot_lsadc"

/* 前视灯开关 */
//#define LED_SWITCH  "/sys/class/gpio/gpio46/value"
#define LED_SWITCH  "/sys/class/pwm/pwmchip12/pwm3/enable"

/* 前视灯亮度 */
#define LED_BRIGHTNESS      "/sys/class/pwm/pwmchip12/pwm3/duty_cycle"

/* 1瓦特 */
#define ONEWATT 13

/* 位数 */
#define DIGIT   8

/* 电压 */
#define INPUT_VOLTAGE 24.0f

struct adc_parameter
{
    int fd;
    int mode;
    int channel;
};


/**
 * printhelp - help.
 *	@none
 */
void printhelp(void)
{
	printf("Usage:adc [mode] [channel]\n");
	printf("options:\n");
	printf("  -m mode \t\t single or continuous\n");
    printf("      0  single\n");
	printf("      1  continuous\n");
	printf("  -c channel \t four channel\n");
	printf("      0  channel\n");
	printf("      1  channel\n");
	printf("      2  channel\n");
	printf("      3  channel\n");
	printf("  -h help \t\t\t Display this information\n");
}

/**
 * __do_abnormal - exception handling.
 *	@str : function name
 */
void __do_abnormal(const char * str)
{
    printf("%s abnormal, program terminated...\n", str);
    exit(0);
}

/**
 * open_light - open light.
 *	@none
 */
int open_light() 
{
    char buf[200] = {0};

    sprintf(buf, "echo %d > %s", 1, LED_SWITCH);
    system(buf); 

    return 0;
}

/**
 * close_light - close light.
 *	@none
 */
int close_light() 
{    
    char buf[200] = {0};

    sprintf(buf, "echo %d > %s", 0, LED_SWITCH);
    system(buf);

    return 0;
}

/**
 * set_light - config led power.
 *	@value : power's scale
 */
int set_light(int value) 
{
    char buf[200] = {0};
    
    if (value > 0) {
        sprintf(buf, "echo %d > %s", value, LED_BRIGHTNESS);
        system(buf);
    }    

    return 0;
}

/**
 * is_open_light - led switch value.
 *	@none
 */
int is_open_light()
{
    FILE *fp = NULL;
    char data[8] = {0};

    fp = fopen(LED_SWITCH, "r");
    if (fp == NULL)  {
        printf("%s open fail...\n", LED_SWITCH);
    }

    fgets(data, 8, fp);
    printf("light was %s!\n", atoi(data) != 0 ? "on" : "off");
    
    fclose(fp);

    return atoi(data);
}

/**
 * open_adc_device - open device.
 *	@mode : 0, SINGLE_STEP; 1, CONTINUOUS
 *  @channel : 0 ~ 3
 */
int open_adc_device(int mode, int channel)
{
    int fd = -1;

    fd = open(DEV_FILE, O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "fail to open file:%s\n", TEMPERATURE_AD);
        return -1;
    }

    if (ioctl(fd, LSADC_IOC_MODEL_SEL, &mode) < 0) {
        fprintf(stderr, "adc model select error.\n");
        goto exit;
    }

    if (ioctl(fd, LSADC_IOC_CHN_ENABLE, &channel) < 0) {
        fprintf(stderr, "enable channel %d error.\n", channel);
        goto exit;
    }

    if (ioctl(fd, LSADC_IOC_START) < 0) {
        fprintf(stderr, "start lsadc error.\n");
        goto exit;
    }

    return fd;

exit:
    if (ioctl(fd, LSADC_IOC_STOP) < 0) {
        fprintf(stderr, "stop lsadc error.\n");
    }

    if (ioctl(fd, LSADC_IOC_CHN_DISABLE, &channel) < 0) {
        fprintf(stderr, "disable channel %d error.\n", channel);
    }

    return -1;
}

/**
 * close_adc_device - close device.
 *	@device_fd : device descriptor 
 */
void close_adc_device(int device_fd)
{
    close(device_fd);
}

/**
 * get_adc_value - obtain adc value.
 *  @fd : device number
 *	@channel : channel 0 ~ 3 
 */
int get_adc_value(int fd, int channel)
{
    int value = 0;

    value = ioctl(fd, LSADC_IOC_GET_CHNVAL, &channel);
    printf("temperature AD value %d\n", value);

    return value;
}

/**
 * obtian_temperature - temperature convert coefficient.
 *  @fd : device number
 *	@channel : channel 0 ~ 3 
 */
float obtian_temperature(int fd, int channel)
{
    float voltage = 0.0f, resistance = 0.0f;
    int adc = 0;
    
    adc = get_adc_value(fd, channel);
    printf("temperature AD value %d\n", adc);

    /* 通过ADC求电压 */
    /* Vref / (2^n-1) = Vresult / raw */
    /*
     * Vref 为标准电压  -> 1.8V
     * n 为 AD 转换的位数   -> 10
     * Vresult 为用户所需要的采集电压   ?
     * raw 为 AD 采集的原始数据 -> atoi(data)
     */    
    voltage = (INPUT_VOLTAGE * adc) / (1024 - 1);
    printf("voltage is %f\n", voltage);
    
    /* 求R2电阻值，利用分压公式 Vout = Vin * R2 / (R1 + R2) */
    /* Vin -> 1.8V, R1 = 10K */
    resistance = (voltage * 10) / (INPUT_VOLTAGE - voltage);
    printf("resistance is %f\n", resistance);   
    
    return resistance;
}

/**
 * obtian_led_power - led PWM value.
 *	@none
 */
float obtian_led_power()
{
    FILE *fp = NULL;
    char data[8] = {0};

    fp = fopen(LED_BRIGHTNESS, "r");
    if (fp == NULL)  {
        printf("%s open fail...\n", LED_BRIGHTNESS);
    }

    fgets(data, 8, fp);
    printf("led PWD value %d\n", atoi(data));
    
    fclose(fp);

    return atoi(data);
}

/**
 * is_temperature_high - temperature determine(温度测定).
 *  @fd : device number
 *	@channel : channel 0 ~ 3 
 */
int is_temperature_high(int fd, int channel)
{
    float resistance = 0.0f; 
    double tempe = 0.0f;

    resistance = obtian_temperature(fd, channel);  

    /* T1 =1/(In(Rt/R)/B+1/T2) = 1/(log(r/10)/3435+1/(273.15+25) */
    /*
     * T1、T2指的是K度即开尔文温度
     * K 273.15(绝对温度) + 摄氏温度 
     * B 3435K
     * T2 = (273.15 + 25)
     *
     * 等价:
     * 1/(log(resistance/10)/3435+1/(273.15+25))                # 10 为NTC R25阻值，即25度时为10K.
     */
    tempe = 1/(log((resistance-0.2)/10)/3435+1/(273.15+25))-273.15;
    printf("temperature = [%lf]\n", tempe);

    if (tempe > 65.0) {
        printf("temperature exceeds the limit((65.0))!\n");
        return 0;
    } 
    else if (tempe < 55.0) {
        printf("temperature below limit(55.0)!\n");
        return 1;
    } 
    else {
        printf("temperature normal...\n");
    }

    return -1;
}

/**
 * adjust - led operation.
 *  @fd : device number
 *	@channel : channel 0 ~ 3 
 */
void adjust(int fd, int channel)
{
    int pwm = 0;     /* 130 is 10W, 104 is 8W */
    int temper_status = 0;
    static int ad_flag = 0; /* 控制降功耗不超过2W */

    printf("################ temperature adjust ################\n");

    pwm = obtian_led_power();
    temper_status = is_temperature_high(fd, channel);

    /* temperature high */
    if (!temper_status) {
        if (ad_flag == 0) {
            set_light(pwm - 13);
            ad_flag = 1;
        }
        else if (ad_flag == 1) {
            set_light(pwm - 13);
            ad_flag = 2;
        }
    }
    /* temperature low */
    else if (temper_status) {
        if (pwm >= 130) { 
            pwm = 130;
            set_light(pwm);
        } else {
            set_light(pwm + 13);
            ad_flag = 0;
        }
    }
}

/**
 * light_control - thread operation.
 *	@param : thread parameter
 */
void * light_control(void * param)
{
    static int count = 1;
    struct adc_parameter *para;

    para = (struct adc_parameter *)param;

	while (1) {
        printf("************ info mode=%d channel=%d************\n", para->mode, para->channel);
        obtian_led_power();
        is_temperature_high(para->fd, para->channel);
        if (count % 60 == 0) {
            //adjust(para->fd, para->channel);
            count = 0;
        }
        sleep(1);
        count++;
	}
}

float ToolsGetMPUTemp()
{
    /* hi3519dv500芯片内部集成温度传感器(T-Sensor)，温度的检测范围为-40~125℃
     * 单次测量模式
     */
    #define MAP_SIZE	4096
    #define MAP_MASK (MAP_SIZE - 1)
    #define MISC_CTRL0 	0x01102A000     // 采集模式寄存器地址
    #define V_MISC_CTRL0	0x80000000  // 设置采集模式寄存器地址的值
    #define MISC_CTRL2		0x01102A008 // 温度记录码寄存器地址 MISC_CTRL2的bit0~bit9有效

    int dev_fd;
    void* p_MISC_CTRL0  = NULL;
    void* p_MISC_CTRL2  = NULL;
    unsigned int tmp = 0;
    double temprature=0;
    long offsetAddr_ctrl0 = MISC_CTRL0 & MAP_MASK;
    long offsetAddr_ctrl2 = MISC_CTRL2 & MAP_MASK;

    dev_fd = open("/dev/mem", O_RDWR | O_NDELAY);
    if (dev_fd < 0)
    {
        printf("open /dev/mem error:%s\n", strerror(errno));
        return 0;
    }
    unsigned int *map_base=(unsigned int *)mmap(NULL, MAP_SIZE, PROT_READ | 
                                                PROT_WRITE, MAP_SHARED, 
                                                dev_fd, 
                                                MISC_CTRL0 & ~MAP_MASK);
    if (NULL == map_base)
    {
        printf("mmap error\n");
        return 0;
    }

    //printf("map_base=0x%08x\n",map_base);
    p_MISC_CTRL0 = map_base+offsetAddr_ctrl0/4;
    p_MISC_CTRL2 = map_base+offsetAddr_ctrl2/4;
    // printf("p_MISC_CTRL0=0x%08x\n",p_MISC_CTRL0);
    // printf("p_MISC_CTRL2=0x%08x\n",p_MISC_CTRL2);

    *(unsigned int*)p_MISC_CTRL0 = V_MISC_CTRL0;
    *(unsigned int*)p_MISC_CTRL0 = 0x0;
    tmp = *(unsigned int*)p_MISC_CTRL2 & 0x000003ff;
    temprature = 1.0*(tmp-132)/808*165-40;
    printf("CPU temprature:%0.2f\n",temprature);
    munmap(map_base, MAP_SIZE);

    if(dev_fd)
        close(dev_fd);

    return temprature;
}

static void ac_sigsegv_handler(int sig, siginfo_t *info, void *ctx)
{
    if (sig == SIGINT) {
        close_light();
        exit(0);
    }    
}

/*
 * abstruct : main program.
 */
int main(int argc, char ** argv)
{
	pthread_t lc_thread_id;
    struct adc_parameter para = {0};
	void * ptr;
	pthread_attr_t attr;
    int mode = 0, channel = 0;
    int c;
    struct option options[] = {
        {"mode", 1, NULL, 'm'},
        {"channel", 1, NULL, 'c'},
        { 0, 0, 0, 0 }
    };
    struct sigaction action;

    if (argc < 5) {
        printhelp();
        exit(1);
    }

    while ((c = 
            getopt_long(argc, argv, "m:c:h", options,
                        NULL)) != -1) {
        switch (c) {
        case 'm':
            mode = atoi(optarg);
            break;
        case 'c':
            channel = atoi(optarg);
            break;
        case 'h':
            printhelp();
            break;
        default:
            printhelp();
        }    
    }
    printf("mode=%d channel=%d\n", mode, channel);

    
    sigemptyset(&action.sa_mask);
    action.sa_sigaction = ac_sigsegv_handler;
    action.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &action, NULL);
    sigaction(SIGINT, &action, NULL);

	pthread_attr_init(&attr);

    #if 0
    /* for run */
    while (!is_open_light()) {
        printf("warn : light off.\n");
        sleep(30);
    }
    #endif
    
    /* for debug */
    if (!is_open_light()) {
        printf("warn : light off.\n");
        open_light();
        set_light(500000*2);
    }
    
    para.fd = open_adc_device(mode, channel);
    para.mode = mode;
    para.channel = channel;

	lc_thread_id = 0;
	pthread_create(&lc_thread_id, &attr, light_control, (void*)&para);
	pthread_join(lc_thread_id, & ptr);

	return 0;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
